/*
 *	MIDAS Context Based Routing
 *	cbr_ip.cc
 *	Copyright (c) 2007 Bartlomiej Zolnierkiewicz
 *	MEAG IT PW
 *
 *	This code is more C-like than C++ since it is easier
 *	to implementent networking features this way and could
 *	be helpful in case if we convert CBR to pure C one day.
 */

#include <sys/types.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <stdlib.h>

#include <linux/if_ether.h>

#include "cbr_ip.h"
#include "cbr_eth.h"

using namespace std;

// choose the one not "conflicting" with other services
#define CBR_IP_PORT 8888

/**
 * Binds RAW IP socket.
 *
 * @param s socket descriptor
 *
 * @return 0 on success, -1 otherwise.
 */

int raw_ip_socket_bind(int s, ip_addr ip) {
cbr_log("raw_ip_socket_bind\n");
		struct sockaddr_in host_addr;

	int on = 1;
	setsockopt(s, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));

	// init the host_address the socket is being bound to
	memset((void*)&host_addr, 0, sizeof(host_addr));

	host_addr.sin_family = AF_INET; // set address family
	host_addr.sin_addr.s_addr = ip; // accept any incoming messages
	host_addr.sin_port = htons(CBR_IP_PORT);// the port it will be bound to
	//	srand(time(NULL));
	//	int port = rand() % 31744 + 1024;
	//	host_addr.sin_port = htons(port);// the port it will be bound to
	//	host_addr.sin_addr.s_addr = ip;

	if (bind(s, (struct sockaddr *)&host_addr, sizeof(host_addr)) < 0) {
		cbr_log("error: cannot bind() IP socket (errno=%d): %s\n",
				errno, strerror(errno));
		close(s);
		return -1;
	}

	return 0;
}

/**
 * Creates RAW IP socket.
 *
 * @param ifname interface name
 *
 * @return socket descriptor number on success, -1 otherwise.
 */

int raw_ip_socket_create(const char *ifname, ip_addr ip) {
cbr_log("raw_ip_socket_create\n");
	//int s = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
		int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	//int s = socket(AF_PACKET, SOCK_DGRAM, 768);

	// raw sockets require root priveleges or CAP_NET_RAW
	if (s == -1) {
		cbr_log("error: IP socket() failed (errno=%d): %s\n",
				errno, strerror(errno));
		return -1;
	}

	if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname) +1)
			== -1) {
		cbr_log("error: setsockopt() SO_BINDTODEVICE failed "
				"(errno=%d): %s\n", errno, strerror(errno));
		return -1;
	}

	if (raw_ip_socket_bind(s, ip) == -1) {
		close(s);
		return -1;
	}

	return s;
}

/**
 * Calculates checksum of the IP packet header.
 *
 * @param buffer pointer to buffer containing the IP packet header
 * @param size size of the IP packet header
 *
 * Calculate 16-bit checksum of the IP packet header.
 *
 * Only @a size == 20 is used currently but lets keep the code generic.
 */

unsigned short ip_hdr_checksum(unsigned short *buffer, int size) {
	unsigned int sum = 0;

	// sum all words together
	while (size > 1) {
		sum += *buffer++;
		size -= 2;
	}
	// add final byte if the size is odd
	if (size)
		sum += *buffer;

	// take only 16 bits out of the 32 bit sum
	sum = (sum >> 16) + (sum & 0xffff);
	sum += (sum >> 16);

	// one's complement the result
	return ~sum;
}

/**
 * Sends RAW IP packet.
 *
 * @param s socket descriptor
 * @param src_ip source IP address
 * @param dst_ip destination IP address
 * @param payload pointer to buffer containing payload
 *
 * Tries to send RAW IP packet of the total size (including header)
 * equal to ETH_DATA_LEN (1500 bytes) from source IP address @a src_ip to
 * the destination IP address @a dst_ip through the socket descriptor @a s.
 *
 * If the payload is less than ETH_DATA_LEN - IP_HDR_SIZE bytes
 * then the rest of the sent bytes are zeroes.
 *
 * @return the number of bytes sent on success, -1 otherwise.
 */

int raw_ip_send(int s, ip_addr src_ip, ip_addr dst_ip, void *payload) {
	void *buffer = malloc(ETH_DATA_LEN);
	void *data = (unsigned char *)buffer + IP_HDR_SIZE;
	struct iphdr *ip = (struct iphdr *)buffer;
	struct sockaddr_in peer;
	int rc = 0;

	//if (cbr_log_addresses()) {
cbr_log	("SEND: source IP=%s\n", iptostr(&src_ip));
	cbr_log("SEND: destination IP=%s\n", iptostr(&dst_ip));
	//}

	if (buffer == NULL) {
		cbr_log("error: malloc() for outgoing IP packet failed"
				" (errno=%d): %s\n", errno, strerror(errno));
		return -1;
	}

	memset(buffer, 0, ETH_DATA_LEN);

	// prepare the header
	//memset(ip, 0, sizeof(*ip));

	ip->version = 4; // version of IP used
	ip->ihl = 5; // Internet Header Length (IHL)
	ip->tos = 0; // Type Of Service (TOS)
	ip->tot_len = // total length of the IP datagram
	htons(ETH_DATA_LEN);
	ip->id = htons(1); // identification
	ip->frag_off = 0; // fragmentation flag
	ip->ttl = 255; // Time To Live (TTL)
	ip->protocol = IPPROTO_RAW; // RAW IP packets
	ip->saddr = src_ip; // source address
	ip->daddr = dst_ip; // destination address
	ip->check = // IP checksum
	ip_hdr_checksum((unsigned short *)ip, IP_HDR_SIZE);

	// copy payload
	memcpy(data, payload, ETH_DATA_LEN - IP_HDR_SIZE);

	memset(&peer, 0, sizeof(peer));

	peer.sin_family = AF_INET;
	//	int port = rand() % 31744 + 1024;
	//	peer.sin_port = htons(port);
	peer.sin_port = htons(CBR_IP_PORT);
	peer.sin_addr.s_addr = dst_ip;

	if (cbr_log_ip_packet())
	dump_cbr_data(ip, ETH_DATA_LEN);

	rc = sendto(s, payload, ETH_DATA_LEN, MSG_DONTROUTE,
			(struct sockaddr *)&peer, sizeof(peer));
	if (rc == -1) {
		free(buffer);
		cbr_log("error: sendto() failed for IP packet (errno=%d):"
				" %s\n", errno, strerror(errno));
		return -1;
	}
	cbr_log(" raw_ip_send sent %d bytes\n", rc);

	free(buffer);

	return rc;
}

/**
 * Receives RAW IP packet.
 *
 * @param s socket descriptor
 * @param payload pointer to buffer containing payload
 * @param src_ip pointer to buffer to put the source IP address
 *
 * Tries to receive RAW IP packet of the total size (including header)
 * equal to ETH_DATA_LEN (1500 bytes) from the socket descriptor @a s.
 *
 * Source IP address is decoded from the IP header and put into the
 * buffer pointed by @a src_ip.  Payload is copied from the IP packet
 * into the buffer pointed by @a payload.
 *
 * @return the number of bytes received on success, -1 otherwise.
 */

int raw_ip_recv(int s, void *payload, ip_addr *src_ip) {
	void *buffer = malloc(ETH_DATA_LEN);
	void *data = (unsigned char *)buffer + IP_HDR_SIZE;
	struct iphdr *ip = (struct iphdr *)buffer;
	int len = 0;

	if (buffer == NULL) {
		cbr_log("error: malloc() for incoming IP packet failed"
				" (errno=%d): %s\n", errno, strerror(errno));
		return -1;
	}

	memset(buffer, 0, ETH_DATA_LEN);

	len = recvfrom(s, buffer, ETH_DATA_LEN, 0, NULL, NULL);

	if (len == -1) {
		cbr_log("error: recvfrom() failed for IP packet (errno=%d):"
				" %s\n", errno, strerror(errno));
		free(buffer);
		return -1;
	}

	// get source IP
	memcpy(src_ip, (char *)buffer + 12, 4);

	if (cbr_log_addresses())
		cbr_log("RECV: source IP=%s\n", iptostr(src_ip));

	if (cbr_log_ip_packet())
		dump_cbr_data(buffer, ETH_DATA_LEN);

	// not our frame, just discard it
	if (ip->protocol != IPPROTO_RAW) {
		if (cbr_log_discarded_packet())
			cbr_log("discardig IP packet - bad protocol (%x)\n",
				ip->protocol);
				free(buffer);
		return 0; /* return success here */
	}

	if (len != ETH_DATA_LEN) {
		cbr_log("error: bad IP packet size (%d)\n", len);
				free(buffer);
		return -1;
	}

	// get payload
	memcpy(payload, data, ETH_DATA_LEN - IP_HDR_SIZE);

	free(buffer);

	return len;
}

/**
 * Sends IP packet encapsulated in Ethernet frame.
 *
 * @param s socket descriptor
 * @param i interface index
 * @param src_mac source MAC address
 * @param dst_mac destination MAC address
 * @param src_ip source IP address
 * @param dst_ip destination IP address
 * @param payload pointer to buffer containing payload
 *
 * Encapsulates IP packet of the total size (including header) equal
 * to ETH_DATA_LEN (1500 bytes), the source IP address @a src_ip and
 * the destination IP address @a dst_ip into Ethernet frame with
 * the source MAC address @a src_mac and the destination MAC address
 * @a dst_mac.  Then it tries to send the Ethernet frame through
 * the socket descriptor @a s and interface number @a i.
 *
 * If the payload is less than ETH_DATA_LEN - IP_HDR_SIZE bytes
 * then the rest of the sent bytes are zeroes.
 *
 * @return the number of bytes sent on success, -1 otherwise.
 */

int eth_ip_send(int s, int i, mac_addr *src_mac, mac_addr *dst_mac,
		ip_addr src_ip, ip_addr dst_ip, void *payload) {
	void *buffer = malloc(ETH_DATA_LEN);
	void *data = (unsigned char *)buffer + IP_HDR_SIZE;
	struct iphdr *ip = (struct iphdr *)buffer;
	struct sockaddr_in peer;
	int rc = 0;

	if (cbr_log_addresses()) {
	cbr_log("SEND: source IP=%s\n", iptostr(&src_ip));
		cbr_log("SEND: destination IP=%s\n", iptostr(&dst_ip));
	}
	
	bool allow = checkRoute(src_ip, dst_ip, src_ip);

	if (!allow)
		return 1;

	if (buffer == NULL) {
		cbr_log("error: malloc() for outgoing IP packet failed"
				" (errno=%d): %s\n", errno, strerror(errno));
		return -1;
	}

	memset(buffer, 0, ETH_DATA_LEN);

	// prepare the header
	memset(ip, 0, sizeof(*ip));

	ip->version = 4; // version of IP used
	ip->ihl = 5; // Internet Header Length (IHL)
	ip->tos = 0; // Type Of Service (TOS)
	ip->tot_len = // total length of the IP datagram
			htons(ETH_DATA_LEN);
	ip->id = htons(1); // identification
	ip->frag_off = 0; // fragmentation flag
	ip->ttl = 255; // Time To Live (TTL)
	ip->protocol = IPPROTO_RAW; // RAW IP packets
	ip->saddr = src_ip; // source address
	ip->daddr = dst_ip; // destination address
	ip->check = // IP checksum
			ip_hdr_checksum((unsigned short *)ip, IP_HDR_SIZE);

	// copy payload
	memcpy(data, payload, ETH_DATA_LEN - IP_HDR_SIZE);

	memset(&peer, 0, sizeof(peer));

	peer.sin_family = AF_INET;
	//	int port = rand() % 31744 + 1024;
	//	peer.sin_port = htons(port);
	peer.sin_port = htons(CBR_IP_PORT);
	peer.sin_addr.s_addr = dst_ip;

	if (cbr_log_ip_packet())
		dump_cbr_data(buffer, ETH_DATA_LEN);

	rc = eth_send(s, i, src_mac, dst_mac, ETH_P_IP, buffer);
	//rc = sendto(s, buffer, ETH_DATA_LEN, MSG_DONTROUTE,
	//		(struct sockaddr *)&peer, sizeof(peer));
	if (rc == -1) {
		free(buffer);
		cbr_log("error: sendto() failed for IP packet (errno=%d):"
				" %s\n", errno, strerror(errno));
		return -1;
	}

	/* adjustment for Ethernet header size */
	rc -= 14;

	cbr_log("  sent %d bytes with eth_ip_send\n", rc);

		free(buffer);

	return rc;
}

/**
 * Extracts IP packet from the received Ethernet frame.
 *
 * @param s socket descriptor
 * @param payload pointer to buffer containing payload
 * @param src_mac source MAC address
 * @param src_ip pointer to buffer to put the source IP address
 *
 * Tries to receive the Ethernet frame from the socket descriptor @a s.
 *  The it tries to extract the IP packet of the total size (including header)
 * equal to ETH_DATA_LEN (1500 bytes) from the received Ethernet frame.
 *
 * Source IP address is decoded from the IP header and put into the buffer
 * pointed by @a src_ip.  Source MAC address is decoded from the Ethernet
 * header and put into the buffer pointed by @a src_mac.  Payload is copied
 * from the IP packet into the buffer pointed by @a payload.
 *
 * @return the number of bytes received on success, -1 otherwise.
 */

int eth_ip_recv(int s, void *payload, mac_addr *src_mac, ip_addr *src_ip, ip_addr my_ip) {
	void *buffer = malloc(ETH_DATA_LEN);
	void *data = (unsigned char *)buffer + IP_HDR_SIZE;
	struct iphdr *ip = (struct iphdr *)buffer;
	int len = 0;
	
	ip_addr *dst_ip = new ip_addr;

	if (buffer == NULL) {
		cbr_log("error: malloc() for incoming IP packet failed"
				" (errno=%d): %s\n", errno, strerror(errno));
		return -1;
	}

	memset(buffer, 0, ETH_DATA_LEN);

	//len = recvfrom(s, buffer, ETH_DATA_LEN, 0, NULL, NULL);
	len = eth_recv(s, buffer, src_mac, ETH_P_IP);

	if (len == -1) {
		cbr_log("error: recvfrom() failed for IP packet (errno=%d):"
				" %s\n", errno, strerror(errno));
		free(buffer);
		return -1;
	}

	// get source IP
	memcpy(src_ip, (char *)buffer + 12, 4);
	memcpy(dst_ip, (char *)buffer + 16, 4);	
	
	bool allow = checkRoute(*src_ip, *dst_ip, my_ip);

	if (!allow)
		return 1;

	if (cbr_log_addresses())
		cbr_log("RECV: source IP=%s destination IP=%s\n", iptostr(src_ip),iptostr(dst_ip));

	if (len == 0) {
		if (cbr_log_discarded_packet())
			cbr_log("discardig IP packet - length is zero\n");
				free(buffer);
		return 0; /* return succes here */
	}

	if (cbr_log_ip_packet())
		dump_cbr_data(buffer, ETH_DATA_LEN);

	// not our frame, just discard it
	if (ip->protocol != IPPROTO_RAW) {
if (cbr_log_discarded_packet())
			cbr_log("discardig IP packet - bad protocol (%x)\n",
				ip->protocol);
				free(buffer);
		return 0; /* return success here */
	}

	/* adjustment for Ethernet header size */
	len -= 14;

	if (len != ETH_DATA_LEN) {
cbr_log("error: bad IP packet size (%d)\n", len);
				free(buffer);
		return -1;
	}

	// get payload
	memcpy(payload, data, ETH_DATA_LEN - IP_HDR_SIZE);

	free(buffer);

	return len;
}

bool checkRoute(ip_addr src_ip, ip_addr dst_ip, ip_addr my_ip) {
	string srcIP = iptostr(&src_ip);
	string dstIP = iptostr(&dst_ip);
	string myIP = iptostr(&my_ip);
	string broadcastIP ("255.255.255.255");

	string route;

	route.append(srcIP);
	route.append(";");
	route.append(dstIP);
	
	string broadcastrule;
		
	broadcastrule.append(myIP);
	broadcastrule.append(";");
	broadcastrule.append(myIP);

	cbr_log("Checking route: %s\n", route.c_str());
	cbr_log("Checking broadcast route: %s\n", broadcastrule.c_str());
	
	string line;

	bool result = true;

	ifstream myfile("./temp/rules.txt");
	if (myfile.is_open()) {
		while (!myfile.eof()) {
			getline(myfile, line);

			//dowolna regula na konkretna trase
			if (line.compare(route) == 0)
				result = false;
			
			//regula na broadcast w postaci my_ip;my_ip
			if(line.compare(broadcastrule) == 0)
			{
				size_t found = route.find(broadcastIP);
				if (found!=string::npos)
					result = false;
			}
		}
		myfile.close();
	}

	if(result)
		cbr_log("Route %s allowed\n", route.c_str());
	else
		cbr_log("Route %s disallowed\n", route.c_str());

	return result;
}